package chase.myrewards;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.SerializableEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.HTTP;

import android.content.Context;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;

import chase.myrewards.beans.Customer;

/**
 * Class that makes the client request and executes it. An instance of this class will be 
 * created when the client tries to send a request to the server for the first time.  The
 * singleton pattern is being used to establish this. The HTTP client in this class shares 
 * its cookies and session data with the web view in InfoActivity.  This class is only used
 * for sending and receiving serialized bean objects from the server and logging out. 
 *  
 * @author Archi Pandurangi
 * @date April 22, 2013
 */
public class MyRewardsClient {

	// Making MyRewardsClient a classic singleton
	private static MyRewardsClient instance = null;

	private String url;
	private ArrayList <NameValuePair> headers;
	private ArrayList <NameValuePair> params;
	private String message;
	private int responseCode;
	private String response;
	private HttpEntity responseEntity;
	private DefaultHttpClient client;
	private Customer customer;
	private boolean loggedIn;
	private long time;
	private int threshold;

	protected MyRewardsClient(){
		this.url = "";
		this.headers = new ArrayList<NameValuePair>();
		this.params = new ArrayList<NameValuePair>();
		this.message = "";
		this.responseCode = 0;
		this.response = null;
		this.responseEntity = null;
		this.client = new DefaultHttpClient();
		this.setCustomer(null);
		this.loggedIn = false;
		this.time = 0;
		this.threshold = 300000; // 5 second automatic logout timeout	
	}

	public static MyRewardsClient getInstance(){
		if (instance == null){
			instance = new MyRewardsClient();
		}
		return instance;
	}

	public void AddParam(String name, String value){
		params.add(new BasicNameValuePair(name, value));
	}

	public void AddHeader(String name, String value){
		headers.add(new BasicNameValuePair(name, value));
	}

	/**
	 * Execute a HTTP request.  Although it doesn't return anything, this method
	 * puts the response in the response member variable.
	 * 
	 * @param method Either RequestType.GET or RequestType.POST
	 * @param html True if the expected response is an html page, False if it is a serialized object.
	 * @throws Exception
	 */
	public void Execute(RequestMethod method, boolean html) throws Exception {
		switch(method) {
			case GET:
			{
				//add parameters
				String combinedParams = "";
				if(!params.isEmpty()){
					combinedParams += "?";
					for(NameValuePair p : params){
						String paramString = p.getName() + "=" + p.getValue();
						if(combinedParams.length() > 1){
							combinedParams  +=  "&" + paramString;
						}
						else{
							combinedParams += paramString;
						}
					}
				}
	
				HttpGet request = new HttpGet(getUrl() + combinedParams);

				//add headers
				for(NameValuePair h : headers){
					request.addHeader(h.getName(), h.getValue());
				}
				if (html){
					executeHtmlRequest(request);
				}
				else{
					executeBeanRequest(request);
				}
				break;
			}
	
			case POST:
			{
				HttpPost request = new HttpPost(getUrl());
	
				//add headers
				for(NameValuePair h : headers){
					request.addHeader(h.getName(), h.getValue());
				}
	
				if(!params.isEmpty()){
					request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
				}
	
				if (html){
					executeHtmlRequest(request);
				}
				else{
					executeBeanRequest(request);
				}
	
				break;
			}
		}
	}
	
	/**
	 * Send the Customer object to the server for the settings to be stored
	 * on the database.
	 */
	public void Logout(){
		Log.d("MyRewardsClient", "in Logout");
		
		try {
			Customer cust = MyRewardsClient.getInstance().getCustomer();
			HttpPost request = new HttpPost(getUrl());
			HttpEntity entity = new SerializableEntity(cust, true);
			request.setEntity(entity);
			executeBeanRequest(request);
		}
		catch(IOException i){
			Log.e("MyRewardsClient", "Logout", i);
		}
	}
	

	public void executeHtmlRequest(HttpUriRequest request){
		//HttpClient client = new DefaultHttpClient();
		client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, System.getProperty("http.agent"));
		HttpResponse httpResponse;

		try {
			httpResponse = client.execute(request);
			responseCode = httpResponse.getStatusLine().getStatusCode();
			message = httpResponse.getStatusLine().getReasonPhrase();
			responseEntity = httpResponse.getEntity();

			if (responseEntity != null) {
				InputStream instream = responseEntity.getContent();
				response = convertStreamToString(instream);

				// Closing the input stream will trigger connection release
				instream.close();
			}
			reset();
		}
		catch (ClientProtocolException e)  {
			Log.e("MyRewardsClient", "ClientProtocolException", e);
		}
		catch (IOException e) {
			Log.e("MyRewardsClient", "IOException", e);
		}
	}
	

	public void executeBeanRequest(HttpUriRequest request){
		Log.d("MyRewardsClient", "in executeBeanRequest");

		//HttpClient client = new DefaultHttpClient();
		client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, System.getProperty("http.agent"));
		
		HttpResponse httpResponse;

		try {
			httpResponse = client.execute(request);
			responseCode = httpResponse.getStatusLine().getStatusCode();
			message = httpResponse.getStatusLine().getReasonPhrase();
			responseEntity = httpResponse.getEntity();

			Log.d("MyRewardsClient", "entity is " + responseEntity.toString());
			Log.d("MyRewardsClient", "entity length " + responseEntity.getContentLength());
			Log.d("MyRewardsClient", "entity type " + responseEntity.getContentType());
			Log.d("MyRewardsClient", "entity stream " + responseEntity.isStreaming());
			
			reset();
		}
		catch (ClientProtocolException e)  {
			Log.e("MyRewardsClient", "ClientProtocolException", e);
		}
		catch (IOException e) {
			Log.e("MyRewardsClient", "IOException", e);
		}
	}
	

	private String convertStreamToString(InputStream is) {
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		StringBuilder sb = new StringBuilder();

		String line = null;
		try {
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
		}
		catch (IOException e) {
			e.printStackTrace();
		}
		finally {
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return sb.toString();
	}
	
	/**
	 * Sync this class' HTTP client's cookies with those of the input parameter.
	 * This method is used with the web view to make the server see only one client.
	 *  
	 * @param ctx The Context (WebView)
	 */
	public void syncSession(final Context ctx){

        List<Cookie> cookies = client.getCookieStore().getCookies();

        if (!cookies.isEmpty()){

            CookieSyncManager.createInstance(ctx);
            CookieManager cookieManager = CookieManager.getInstance();

            //Sync all the cookies in the HttpClient with the WebView by generating cookie string
            for (Cookie cookie : cookies){

                Cookie sessionInfo = cookie;

                String cookieString = sessionInfo.getName() + "=" + sessionInfo.getValue() + "; domain=" + sessionInfo.getDomain();
                cookieManager.setCookie(getUrl(), cookieString);
                CookieSyncManager.getInstance().sync();
            }
        }
	}

	/**
	 * Clear the query parameters and request headers.  Does not change the url.
	 */
	public void reset(){
		clearParams();
		clearHeaders();
	}	

	public ArrayList<NameValuePair> getParams() {
		return params;
	}
	

	public void setParams(ArrayList<NameValuePair> params) {
		this.params = params;
	}

	
	public void clearParams(){
		this.params.clear();
	}

	
	public ArrayList<NameValuePair> getHeaders() {
		return headers;
	}

	
	public void setHeaders(ArrayList<NameValuePair> headers) {
		this.headers = headers;
	}

	
	public void clearHeaders(){
		this.headers.clear();
	}

	
	public String getUrl() {
		return url;
	}

	
	public void setUrl(String url) {
		this.url = url;
	}

	
	public HttpEntity getResponseEntity() {
		return responseEntity;
	}

	
	public void setResponseEntity(HttpEntity responseEntity) {
		this.responseEntity = responseEntity;
	}

	
	public String getResponse() {
		return response;
	}

	
	public void setResponse(String response) {
		this.response = response;
	}

	
	public int getResponseCode() {
		return responseCode;
	}

	
	public void setResponseCode(int responseCode) {
		this.responseCode = responseCode;
	}

	
	public String getMessage() {
		return message;
	}

	
	public void setMessage(String message) {
		this.message = message;
	}

	
	public boolean isLoggedIn() {
		return loggedIn;
	}

	
	public void setLoggedIn(boolean loggedIn) {
		this.loggedIn = loggedIn;
	}

	
	public long getTime() {
		return time;
	}

	
	public void setTime(long time) {
		this.time = time;
	}

	
	public int getThreshold() {
		return threshold;
	}

	
	public void setThreshold(int threshold) {
		this.threshold = threshold;
	}

	
	public void setCustomer(Customer customer) {
		this.customer = customer;
	}

	
	public Customer getCustomer() {
		return customer;
	}
}
